package org.yoshiori.lingr.api;

import java.io.IOException;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.yoshiori.lingr.api.vo.ClientType;
import org.yoshiori.lingr.api.vo.MessageType;
import org.yoshiori.lingr.api.vo.Source;

/**
 * @author yoshiori
 * 
 */
public class Lingr {
	private static final Log log = LogFactory.getLog(Lingr.class);

	// private static final SimpleDateFormat FORMAT = new SimpleDateFormat(
	// "yyyy-MM-dd'T'hh:mm:ssZ");

	private final String name;

	private final String apiKey;

	private String session;

	private String ticket;

	private String counter;

	private String occupantId;

	private int maxObserveTime;

	private boolean isAuth;

	/**
	 * @param name
	 * @param apiKey
	 */
	public Lingr(String name, String apiKey) {
		this.name = name;
		this.apiKey = apiKey;
	}
	/**
	 * @param apiKey
	 */
	public Lingr(String apiKey) {
		this.name = null;
		this.apiKey = apiKey;
	}

	/**
	 * @return
	 */
	public boolean create() {
		PostMethod post = new PostMethod(
				"http://www.lingr.com/api/session/create/");
		post.addParameter("api_key", apiKey);
		Element root = executeMethod(post).getRootElement();

		session = root.elementText("session");
		if (session == null) {
			throw new LingrException(root.element("error").elementText(
					"message"));
		}
		if (log.isDebugEnabled()) {
			log.debug("session start - session : " + session);
		}
		return true;
	}

	/**
	 * @return
	 */
	public boolean destroy() {
		if (ticket != null) {
			exit();
		}
		if(isAuth){
			logout();
		}
		PostMethod post = new PostMethod(
				"http://www.lingr.com/api/session/destroy/");
		post.setParameter("session", session);
		String status = executeMethod(post).getRootElement().elementText(
				"status");
		if (log.isDebugEnabled()) {
			log.debug("destroy - status : " + status);
		}
		if ("ok".equals(status)) {
			session = null;
			return true;
		}
		return false;
	}

	/**
	 * @return
	 */
	public boolean exit() {
		PostMethod post = new PostMethod("http://www.lingr.com/api/room/exit");
		post.setParameter("session", session);
		post.setParameter("ticket", ticket);
		String status = executeMethod(post).getRootElement().elementText(
				"status");
		if (log.isDebugEnabled()) {
			log.debug("exit - status : " + status);
		}
		if ("ok".equals(status)) {
			ticket = null;
			return true;
		}
		return false;
	}

	/**
	 * @param email
	 * @param password
	 * @return
	 */
	public boolean login(String email, String password) {
		PostMethod post = new PostMethod("http://www.lingr.com/api/auth/login/");
		post.setParameter("session", session);
		post.setParameter("email", email);
		post.setParameter("password", password);
		isAuth = "ok".equals(executeMethod(post).getRootElement().elementText(
				"status"));
		return isAuth;
	}

	/**
	 * 
	 */
	public void logout() {
		if (isAuth) {
			PostMethod post = new PostMethod(
					"http://www.lingr.com/api/auth/logout/");
			post.setParameter("session", session);
			isAuth = !"ok".equals(executeMethod(post).getRootElement().elementText("status"));
		}
	}

	/**
	 * @param message
	 * @return
	 */
	public boolean say(String message) {
		PostMethod post = new PostMethod("http://www.lingr.com/api/room/say");
		post.setParameter("session", session);
		post.setParameter("ticket", ticket);
		post.setParameter("message", message);
		Element root = executeMethod(post).getRootElement();
		String status = root.elementText("status");
		if (log.isDebugEnabled()) {
			log.debug("say - status : " + status);
		}
		if ("ok".equals(status)) {
			return true;
		} else {
			log.error("Error say : "
					+ root.element("error").elementText("message"));
		}

		return false;
	}

	/**
	 * @param room
	 * @return
	 */
	public boolean enter(String room) {
		PostMethod post = new PostMethod("http://www.lingr.com/api/room/enter");
		post.setParameter("session", session);
		post.setParameter("id", room);
		if(name != null){
			post.setParameter("nickname", name);
		}
		Element root = executeMethod(post).getRootElement();
		ticket = root.elementText("ticket");
		maxObserveTime = Integer.parseInt(root.elementText("max_observe_time"));
		occupantId = root.elementText("occupant_id");
		counter = root.element("room").elementText("counter");
		if (log.isDebugEnabled()) {
			log.debug("enter - ticket : " + ticket + ", occupant_id : "
					+ occupantId + ", counter : " + counter
					+ ", max_observe_time : " + maxObserveTime);
		}
		return ticket != null;
	}

	/**
	 * @return
	 */
	public ObserveEvent observe() {
		if (log.isDebugEnabled()) {
			log.debug("observe - start");
		}
		GetMethod get = new GetMethod("http://www.lingr.com/api/room/observe/");
		get.setQueryString(new NameValuePair[] {
				new NameValuePair("session", session),
				new NameValuePair("ticket", ticket),
				new NameValuePair("counter", counter) });
		Element root = executeMethod(get).getRootElement();
		if ("ok".equals(root.elementText("status"))) {
			String _counter = root.elementText("counter");
			if (_counter != null) {
				counter = _counter;
			}
		} else {
			throw new LingrObserveException(root.element("error").elementText(
					"message"));
		}
		if (log.isDebugEnabled()) {
			log.debug("observe - end");
		}
		return new ObserveEvent(parseMessages(root), parseOccupants(root));
	}

	private Set<Occupant> parseOccupants(Element root) {
		Set<Occupant> Occupants = new HashSet<Occupant>();
		Element occupantsElement = root.element("occupants");
		if (occupantsElement != null) {
			for (Iterator i = occupantsElement.elementIterator("occupant"); i
					.hasNext();) {
				Element messageElement = (Element) i.next();
				Occupant occupant = new Occupant();
				occupant.setId(messageElement.elementText("id"));
				occupant.setNickname(messageElement.elementText("nickname"));
				occupant.setSource(Source.lookup(messageElement
						.elementText("source")));
				occupant.setClientType(ClientType.lookup(messageElement
						.elementText("client_type")));
				occupant.setIconURL(messageElement.elementText("icon_url"));

				if (!occupant.getId().equals(occupantId)) {
					Occupants.add(occupant);
				}
			}
		}
		return Occupants;
	}

	/**
	 * @param root
	 * @return
	 */
	private Set<Message> parseMessages(Element root) {
		Set<Message> messages = new HashSet<Message>();
		Element messagesElement = root.element("messages");
		if (messagesElement != null) {
			for (Iterator i = messagesElement.elementIterator("message"); i
					.hasNext();) {
				Element messageElement = (Element) i.next();
				Message message = new Message();
				message.setId(messageElement.elementText("id"));
				message.setType(MessageType.lookup(messageElement
						.elementText("type")));
				message
						.setOccupantId(messageElement
								.elementText("occupant_id"));
				message.setNickname(messageElement.elementText("nickname"));
				message.setSource(Source.lookup(messageElement
						.elementText("source")));
				message.setClientType(ClientType.lookup(messageElement
						.elementText("client_type")));
				message.setIconURL(messageElement.elementText("icon_url"));

				// sunもapacheもISOのparseに対応してない･･･orz
				// message.setTimstamp(FORMAT.parse(messageElement.elementText("timestamp")));
				// message.setTimstamp((Date)
				// DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.parseObject(messageElement.elementText("timestamp")));
				// とりあえずの処理
				message.setTimestamp(new Date());
				message.setText(messageElement.elementText("text"));

				if (!message.getOccupantId().equals(occupantId)) {
					messages.add(message);
				}
			}
		}
		return messages;
	}

	/**
	 * @param method
	 * @return
	 */
	private Document executeMethod(HttpMethod method) {
		try {
			method.setRequestHeader("Content-Type",
					"application/x-www-form-urlencoded; charset=UTF-8");
			HttpClient client = new HttpClient();
			if (maxObserveTime != 0) {
				client.getHttpConnectionManager().getParams().setSoTimeout(
						(maxObserveTime * 1000) + (1000 * 60));
			}
			client.executeMethod(method);
			return new SAXReader().read(method.getResponseBodyAsStream());
		} catch (IOException e) {
			throw new LingrException(e);
		} catch (DocumentException e) {
			throw new LingrException(e);
		} finally {
			method.releaseConnection();
		}
	}

	/**
	 * @return
	 */
	public boolean isAlive() {
		return session != null && ticket != null;
	}
}
